package fr.cephb.joperon.core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.sleepycat.db.DatabaseEntry;
import com.sleepycat.db.DatabaseException;
import com.sleepycat.db.OperationStatus;
import com.sleepycat.db.Transaction;


import fr.cephb.berkeley.ByteReader;
import fr.cephb.berkeley.ByteWriter;
import fr.cephb.berkeley.DBUtils;
import fr.cephb.joperon.core.iterator.DatabaseValueIterator;
import fr.cephb.joperon.core.iterator.EqualRangeIterator;
import fr.cephb.lang.UInt32;
import fr.cephb.util.CloseableIterator;

/**
 * A specialized abastract OperonDB allowing duplicate keys
 * @author lindenb
 *
 * @param <K> the key
 * @param <V> the value
 */
public abstract class OperonDBDuplicateValue<K extends Comparable<K>,V> extends OperonDB<K,V>
	{
	/** constructor with operon */
	protected OperonDBDuplicateValue(Operon operon)
		{
		super(operon);
		}

	/** return always true */
	public final boolean isAllowingDuplicates()
		{
		return true;
		}

	/** put a key, value in the database */
	public final OperationStatus put(K key,V value) throws DatabaseException
		{
		return put(null,key,value);
		}
	/** put a key, value in the database with a transaction */
	public OperationStatus put(Transaction tx,K key,V value) throws DatabaseException
		{
		DatabaseEntry ek = keyToEntry(key);
		DatabaseEntry ev = valueToEntry(value);
		return getDatabase().put(tx, ek, ev);
		}
	
	/** returns wether the database contains the given key */
	public boolean containsKey(K key) throws DatabaseException
		{
		CloseableIterator<V> iter= listValues(key);
		boolean ok=iter.hasNext();
		iter.close();
		return ok;
		}
	
	
	/** list over all the values for the given key */
	public DatabaseValueIterator<V> listValues(K key) throws DatabaseException
		{
		EqualRangeIterator kri= new EqualRangeIterator(cursor(),keyToEntry(key));
		return new DatabaseValueIterator<V>(kri)
			{
			@Override
			protected V extractFromEntries(DatabaseEntry key,DatabaseEntry value)
					throws DatabaseException
				{
				return readValue(key, value);
				}
			};
		}
	
	/** return a list of all the values for the given key */
	public List<V> get(K key)  throws DatabaseException
		{
		return listValues(key).asList();
		}
	

	
	/** return a list of all the values for the given key */
	public Map<K,List<V>> get(Set<K> keys)  throws DatabaseException
		{
		HashMap<K, List<V>> map= new HashMap<K, List<V>>();
		for(K key:keys)
			{
			map.put(key, new ArrayList<V>(get(key)));
			}
		return map;
		}
	
	/** a specialized OperonDBDuplicateValue for java.lang.String */
	public static abstract class STRING<V>
		extends OperonDBDuplicateValue<String, V>
		{
		public STRING(Operon operon)
			{
			super(operon);
			}
		
		@Override
		public String keyFromString(String s) {
			return s;
			}
		
		@Override
		protected DatabaseEntry keyToEntry(String key) throws DatabaseException {
			return new DatabaseEntry(DBUtils.string2CString(key));
			}
		@Override
		public String readKey(DatabaseEntry key, DatabaseEntry data)
				throws DatabaseException {
			return new ByteReader(key).readCString();
			}
		}
	
	/** a specialized OperonDBDuplicateValue for java.lang.String case insensible (toLowerCase) */
	public static abstract class ISTRING<V>
	extends STRING<V>
		{
		public ISTRING(Operon operon)
			{
			super(operon);
			}
		
		@Override
		public String keyFromString(String key) {
			return super.keyFromString(key.toLowerCase());
			}
		
		
		@Override
		protected DatabaseEntry keyToEntry(String key)
				throws DatabaseException {
			return super.keyToEntry(key.toLowerCase());
			}
		}
	
	/** a specialized OperonDBDuplicateValue for java.lang.Integer */
	public static abstract class INTEGER<V>
	extends OperonDBDuplicateValue<Integer, V>
		{
		public INTEGER(Operon operon)
			{
			super(operon);
			}
		
		@Override
		public Integer keyFromString(String s) {
			return new Integer(s);
			}
		
		@Override
		protected DatabaseEntry keyToEntry(Integer key) throws DatabaseException
			{
			ByteWriter w= new ByteWriter();
			w.writeInt32(key);
			return w.getEntry();
			}
		@Override
		public Integer readKey(DatabaseEntry key, DatabaseEntry data)
				throws DatabaseException {
			return new ByteReader(key).readInt32();
			}
		
		}
	
	/** a specialized OperonDBDuplicateValue for java.lang.Long */
	public static abstract class LONG<V>
	extends OperonDBDuplicateValue<Long, V>
		{
		public LONG(Operon operon)
			{
			super(operon);
			}
		
		@Override
		public Long keyFromString(String s) {
			return new Long(s);
			}
		
		@Override
		protected DatabaseEntry keyToEntry(Long key) throws DatabaseException {
			ByteWriter w= new ByteWriter();
			w.writeInt64(key);
			return w.getEntry();
			}
		@Override
		public Long readKey(DatabaseEntry key, DatabaseEntry data)
				throws DatabaseException {
			return new ByteReader(key).readInt64();
			}
		
		}
	
	/** a specialized OperonDBDuplicateValue for UINT32 */
	public static abstract class UINT32<V>
	extends OperonDBDuplicateValue<fr.cephb.lang.UInt32, V>
		{
		public UINT32(Operon operon)
			{
			super(operon);
			}
		
		
		public List<V> get(long key) throws DatabaseException {
			return get(new UInt32(key));
			}
		
		public KeyValueIterator listKeyValues(long start,long end, boolean includeEnd) throws DatabaseException
			{
			return listKeyValues(new UInt32(start),new UInt32(end),includeEnd);
			}
		
		public DatabaseValueIterator<V> listValues(long key)
				throws DatabaseException
			{
			return listValues(new UInt32(key));
			}
		
		@Override
		public UInt32 keyFromString(String s) {
			return new UInt32(s);
			}
		
		@Override
		protected DatabaseEntry keyToEntry(fr.cephb.lang.UInt32 key) throws DatabaseException {
			return new DatabaseEntry(new ByteWriter().writeUInt32(key).getBytes());
			}
		
		@Override
		public UInt32 readKey(DatabaseEntry key, DatabaseEntry data)
				throws DatabaseException {
			return new UInt32(new ByteReader(key).readUInt32());
			}
		
		
		}
	
	
	}
